﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NeturalMath
{
    /// <summary>
    /// Built in data type to represent complex values
    /// </summary>
    public sealed class ComplexValue:NumberValue
    {
        /// <summary>
        /// Creates a new instance of the complex type
        /// </summary>
        /// <param name="real"></param>
        /// <param name="i"></param>
        internal ComplexValue(NumberValue real, NumberValue i,MathRuntime runtime)
            : base(ValueTypes.Complex,"complex",real.Value,runtime)
        {
            Real = real;
            I = i;

            AbsValue = (NumberValue)PowerOperator((Real * Real + I*I), runtime.NewNumber(.5));
        }

        public NumberValue Real { get; private set; }

        public NumberValue I { get; private set; }

        public NumberValue AbsValue { get; private set; }

        #region Logical Operators

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue And(MathValue arg)
        {
            var other = (ComplexValue)arg;
            var real = Real & other.Real;
            var i = I & other.I;
            return Runtime.NewComplex((NumberValue)real, (NumberValue)i);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Or(MathValue arg)
        {
            var other = (ComplexValue)arg;
            var real = Real | other.Real;
            var i = I | other.I;
            return Runtime.NewComplex((NumberValue)real, (NumberValue)i);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Xor(MathValue arg)
        {
            var other = (ComplexValue)arg;
            var real = Real ^ other.Real;
            var i = I ^ other.I;
            return Runtime.NewComplex((NumberValue)real, (NumberValue)i);
        }

        #endregion

        #region Relational Methods

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected override bool IsTrue()
        {
            var r = Real.ConvertToBoolean();
            var i = I.ConvertToBoolean();
            return (bool)(r & i).Value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected override MathValue ToMinus()
        {
            return Runtime.NewComplex((NumberValue)(-Real), (NumberValue)(-I));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override bool? IsLessThan(MathValue arg)
        {
            var other = (ComplexValue)arg;
            return AbsValue.Value < other.AbsValue.Value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override bool? IsGreaterThan(MathValue arg)
        {
            var other = (ComplexValue)arg;
            return AbsValue.Value > other.AbsValue.Value;
        }

        #endregion

        #region Basic Math Methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Add(MathValue arg)
        {
            var other = (ComplexValue)arg;
            return Runtime.NewComplex((NumberValue)(Real + other.Real), (NumberValue)(I + other.I));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Subtract(MathValue arg)
        {
            var other = (ComplexValue)arg;
            return Runtime.NewComplex((NumberValue)(Real - other.Real), (NumberValue)(I - other.I));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Multiply(MathValue arg)
        {
            //(x + yi)(u + vi) = (xu – yv) + (xv + yu)i. 
            var other = (ComplexValue)arg;
            return Runtime.NewComplex((NumberValue)(Real * other.Real + I * other.I), (NumberValue)(Real * other.I + I * other.Real));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Divide(MathValue arg)
        {
            // x + yi     (xu + yv) + (–xv + yu)i
            // ------  =  ----------------------
            // u + vi 		    u^2 + v^2
            var other = (ComplexValue)arg;

            var bottom = PowerOperator(other.Real, Runtime.NewNumber(2)) + PowerOperator(other.I, Runtime.NewNumber(2));

            return Runtime.NewComplex((NumberValue)((Real * other.Real+I * other.I)/bottom), (NumberValue)((-Real*other.I + I * other.Real)/bottom));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Modulo(MathValue arg)
        {
            var other = (ComplexValue)arg;

            var div = (ComplexValue)(this / other);
            var rFactor = Math.Truncate(div.Real.Value);
            var iFactor = Math.Truncate(div.I.Value);
            var rRound = rFactor * Real.Value;
            var iRound = iFactor * I.Value;

            var real = div.Real.Value - rRound;
            var i = div.I.Value - iRound;

            return Runtime.NewComplex(real, i);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Power(MathValue arg)
        {
            var other = (ComplexValue)arg;

            var power = (int)other.Real.Value;
            var rA = Real.Value;
            var rB = other.Real.Value;
            var iA = I.Value;
            var iB = other.I.Value;

            if (power == rB && power >= 0 && iB == 0)
            {
                var result = Runtime.NewComplex(1, 0);
                if (power == 0) return result;
                var factor = this;
                while (power != 0)
                {
                    if ((power & 1) != 0)
                    {
                        result = result * factor;
                    }
                    factor = (ComplexValue)(factor * factor);
                    power >>= 1;
                }
                return result;
            }

            if (rA == 0 && iA == 0)
            {
                return (rB == 0 && iB == 0)
                    ? Runtime.NewComplex(1, 0)
                    : Runtime.NewComplex(0, 1);
            }

            var powers = rA * rA + iA * iA;
            var a = Math.Atan2(iA, rA);
            var mul = Math.Pow(powers, rB / 2) * Math.Exp(-iB * a);
            var common = rB * a + .5 * iB * Math.Log(powers);

            return Runtime.NewComplex(mul * Math.Cos(common), mul * Math.Sin(common));
        }

        #endregion

        #region Conversion Methods

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToString()
        {
            return Runtime.NewString(GetToString());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToNumber()
        {
            return AbsValue;
        }

        protected internal override MathValue ConvertToComplex()
        {
            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToBoolean()
        {
            return (Real.ConvertToBoolean() & I.ConvertToBoolean());
        }

        protected internal override MathValue ConvertToRange()
        {
            return Runtime.NewRange(Real, AbsValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue ConvertToNativeValue(MathValue arg)
        {
            return arg.ConvertToComplex();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected override string GetToString()
        {
            return string.Format("({0},{1}i)", Real, I);
        }

        #endregion
    }
}
